﻿using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text;
using System.Text.RegularExpressions;
using DimensionsHelper.Common.Utils;
using DimensionsHelper.LanguageService.Protocol;

namespace DimensionsHelper.LanguageService.Syntax;

/// <summary>
///     管理源代码文件的类型，通过文件路径名索引。
/// </summary>
[DebuggerDisplay("{Root,nq}")]
public partial class SourceFileManager : IDisposable
{
    private readonly Dictionary<DocumentUri, SourceTextFile> _files;
    private readonly Dictionary<DocumentUri, ReferenceDirectiveInfo[]> _referenceDirectives;
    private bool _disposed;
    private FileSystemWatcher _watcher;


    public SourceFileManager(string root)
    {
        Root = root;
        RootUri = DocumentUriCache.GetCached(root);

        _files = new Dictionary<DocumentUri, SourceTextFile>(DocumentUriComparer.Default);
        _referenceDirectives = new Dictionary<DocumentUri, ReferenceDirectiveInfo[]>(DocumentUriComparer.Default);

        InitializeWatcher();
    }


    public SourceFileManager(DocumentUri rootUri)
    {
        Root = DocumentUri.UriToFsPath(rootUri, true);
        RootUri = rootUri;

        _files = new Dictionary<DocumentUri, SourceTextFile>(DocumentUriComparer.Default);
        _referenceDirectives = new Dictionary<DocumentUri, ReferenceDirectiveInfo[]>(DocumentUriComparer.Default);

        InitializeWatcher();
    }


    /// <summary>
    ///     文件根路径
    /// </summary>
    public string Root { get; private init; }


    /// <summary>
    ///     文件根Uri
    /// </summary>
    public DocumentUri RootUri { get; private set; }

    public void Dispose()
    {
        if (_disposed)
        {
            return;
        }

        Clear();
        _watcher.EnableRaisingEvents = false;
        _watcher.Dispose();
        GC.SuppressFinalize(this);
        _disposed = true;
    }


    [MemberNotNull(nameof(_watcher))]
    private void InitializeWatcher()
    {
        _watcher = new FileSystemWatcher(Root)
        {
            IncludeSubdirectories = true
        };

        _watcher.Filters.Add("*.dms");
        _watcher.Filters.Add("*.mrs");
        _watcher.Filters.Add("*.inc");

        _watcher.Created += OnFileCreated;
        _watcher.Deleted += OnFileDeleted;
        _watcher.Renamed += OnFileRenamed;

        _watcher.EnableRaisingEvents = true;
    }

    private void OnFileRenamed(object sender, RenamedEventArgs e)
    {
        if (!string.IsNullOrEmpty(e.OldName))
        {
            RemoveFile(e.OldName);
        }

        ProcessFile(e.FullPath);
    }

    private void OnFileDeleted(object sender, FileSystemEventArgs e)
    {
        RemoveFile(e.FullPath);
    }

    private void OnFileCreated(object sender, FileSystemEventArgs e)
    {
        ProcessFile(e.FullPath);
    }


    [GeneratedRegex(@"\.(mrs|dms|inc)$", RegexOptions.IgnoreCase, "zh-CN")]
    private static partial Regex GetExtensionRegex();


    private static bool IsUsefulExtension(string extension)
    {
        return GetExtensionRegex().IsMatch(extension);
    }


    /// <summary>
    ///     清空已读取的内容
    /// </summary>
    public void Clear()
    {
        _files.Clear();
        _referenceDirectives.Clear();
    }


    private static void ProcessSourceFile(SourceTextFile? source)
    {
        if (source == null)
        {
            return;
        }

        if (source.Preprocessed == null || source.TimeStamp > source.Preprocessed.TimeStamp)
        {
            source.Preprocessed = Preprocessor.Process(source);
        }
    }


    private void ProcessFile(string file)
    {
        if (TryLoadSourceFile(GetFullPath(file), out SourceTextFile? source))
        {
            ProcessSourceFile(source);
        }
    }


    private SourceTextFile? LoadSourceTextFile(string fileName)
    {
        return TryLoadSourceFile(GetFullPath(fileName), out SourceTextFile? source) ? source : null;
    }


    /// <summary>
    ///     由于有reference的注释指令，需要在预处理时，优先读取文件的依赖关系。
    ///     当文件带有引用指令时，不预处理此文件，方法返回<see langword="null" />。
    /// </summary>
    private void ReadCommentDirective(SourceTextFile? source)
    {
        if (source == null)
        {
            return;
        }

        IReadOnlyList<Directive> directives = Preprocessor.ReadCommentDirectives(source);
        foreach (Directive? directive in directives)
        {
            if (directive is not { DirectiveKind: DirectiveKinds.Reference, FilePathNode: not null } ||
                string.IsNullOrEmpty(directive.FilePathNode.Text) ||
                string.IsNullOrEmpty(directive.RelativeNameNode?.Text))
            {
                continue;
            }

            ResolveReferenceDirective(directive, source.Directory, source);
            return;
        }
    }


    /// <summary>
    ///     初始化根路径下的所有源文件
    /// </summary>
    public void Initialize()
    {
        Clear();
        SourceTextFile?[] sources = Directory.GetFiles(Root, "*.*", SearchOption.AllDirectories)
            .Where(IsUsefulExtension)
            .Select(LoadSourceTextFile)
            .ToArray();

        // 分两次遍历
        sources.ForEach(ReadCommentDirective);
        sources.ForEach(src =>
        {
            // 只处理入口文件即可
            if (src?.IsEntry == true)
            {
                ProcessSourceFile(src);
            }
        });
    }


    /// <summary>
    ///     获取当前的文件列表。
    /// </summary>
    public IEnumerable<SourceTextFile> GetFiles()
    {
        return _files.Values;
    }


    private void ResolveReferenceDirective(Directive directive,
        string directory, SourceTextFile source)
    {
        string filePath = directive.FilePathNode!.Text;
        string relativeName = directive.RelativeNameNode!.Text;
        string resolvedPath = Path.GetFullPath(filePath, directory);
        DocumentUri relativeUri = DocumentUriCache.GetCached(relativeName);

        if (_referenceDirectives.TryGetValue(relativeUri, out ReferenceDirectiveInfo[]? arr))
        {
            if (Array.Exists(arr, p =>
                    p.Source == source &&
                    p.TargetFileName.Equals(resolvedPath, StringComparison.OrdinalIgnoreCase)))
            {
                return;
            }

            Array.Resize(ref arr, arr.Length);
            arr[^1] = new ReferenceDirectiveInfo(resolvedPath, source);
        }
        else
        {
            _referenceDirectives.Add(relativeUri,
                [new ReferenceDirectiveInfo(resolvedPath, source)]);
        }
    }


    /// <summary>
    ///     查找根目录下所有文件中声明的指定关联宏名的注释指令列表。
    /// </summary>
    public ImmutableArray<(string, SourceTextFile)> GetReferences(string relativeName)
    {
        return _referenceDirectives.TryGetValue(DocumentUriCache.GetCached(relativeName),
            out ReferenceDirectiveInfo[]? infos)
            ? [..infos.Select(e => (e.TargetFileName, e.Source))]
            : ImmutableArray<(string, SourceTextFile)>.Empty;
    }


    internal string GetFullPath(string path)
    {
        return !Path.IsPathRooted(path) ? Path.GetFullPath(path, Root) : path;
    }


    /// <summary>
    ///     创建或获取指定路径的文本文件
    /// </summary>
    /// <param name="uri">文件URI</param>
    /// <returns></returns>
    /// <exception cref="FileNotFoundException"></exception>
    /// <exception cref="Exception"></exception>
    public SourceTextFile GetFileByPath(DocumentUri uri)
    {
        if (_files.TryGetValue(uri, out SourceTextFile? file))
        {
            return file;
        }

        string path = DocumentUri.UriToFsPath(uri, true);

        if (!File.Exists(path))
        {
            throw new FileNotFoundException(path);
        }

        if (TryLoadSourceFile(uri, out SourceTextFile? source))
        {
            return source;
        }

        throw new Exception($"File : {path} load failed.");
    }


    /// <summary>
    ///     <inheritdoc cref="GetFileByPath(DocumentUri)" />
    /// </summary>
    /// <param name="path">文件相对或绝对路径</param>
    public SourceTextFile GetFileByPath(string path)
    {
        DocumentUri uri = DocumentUriCache.GetCached(GetFullPath(path));
        return GetFileByPath(uri);
    }


    /// <summary>
    ///     尝试获取指定路径的已有文件，此方法不会添加新的文件。
    /// </summary>
    /// <param name="uri">文件URI</param>
    /// <param name="file">查找到的源文件</param>
    public bool TryGetFileByPath(DocumentUri uri,
        [MaybeNullWhen(false)] out SourceTextFile file)
    {
        return _files.TryGetValue(uri, out file);
    }


    /// <summary>
    ///     <inheritdoc cref="TryGetFileByPath(DocumentUri, out SourceTextFile)" />
    /// </summary>
    public bool TryGetFileByPath(string path,
        [MaybeNullWhen(false)] out SourceTextFile file)
    {
        return TryGetFileByPath(DocumentUriCache.GetCached(GetFullPath(path)), out file);
    }


    /// <summary>
    ///     尝试添加一个新的文件，如果指定文件名已存在，返回<see langword="false" />
    /// </summary>
    /// <param name="file">需要添加的源文件</param>
    public bool TryAddFile(SourceTextFile file)
    {
        return _files.TryAdd(DocumentUriCache.GetCached(GetFullPath(file.FileName)), file);
    }


    /// <summary>
    ///     尝试创建一个新的文件并添加到文件集合中，如果指定文件路径已存在，返回<see langword="false" />
    /// </summary>
    /// <param name="path">指定文件路径</param>
    /// <param name="content">指定文本内容</param>
    /// <param name="sourceFile">创建好的源文件对象</param>
    public bool TryCreateSourceFile(string path, string content,
        [MaybeNullWhen(false)] out SourceTextFile sourceFile)
    {
        sourceFile = null;
        SourceTextFile src = new(this, path, content);

        if (!TryAddFile(src))
        {
            return false;
        }

        sourceFile = src;
        return true;
    }


    /// <summary>
    ///     尝试读取指定路径的源文件。
    /// </summary>
    /// <param name="uri">指定文件Uri</param>
    /// <param name="source">已有的或新建的源文件对象</param>
    /// <remarks>
    ///     此方法会先检查是否已有指定路径的文件，如果已有，返回已有文件；
    ///     否则，创建一个新的，添加到集合中并返回。
    /// </remarks>
    public bool TryLoadSourceFile(DocumentUri uri,
        [MaybeNullWhen(false)] out SourceTextFile source)
    {
        if (_files.TryGetValue(uri, out source))
        {
            return true;
        }

        string fsPath = DocumentUri.UriToFsPath(uri, true);

        if (!File.Exists(fsPath))
        {
            return false;
        }

        using FileStream fs = File.Open(fsPath, FileMode.Open);
        Encoding encoding = FileHelper.DetectFileEncoding(fs);
        fs.Seek(0, SeekOrigin.Begin);
        using StreamReader sr = new(fs, encoding);
        source = new SourceTextFile(this, fsPath, sr.ReadToEnd(), encoding);
        sr.Close();
        fs.Close();
        _files.Add(uri, source);
        return true;
    }


    /// <summary>
    ///     尝试使用文件路径获取源文件对象。
    /// </summary>
    /// <param name="path">指定文件路径，可以是绝对路径或相对路径</param>
    /// <param name="source">已有的或新建的源文件对象</param>
    /// <remarks>
    ///     此方法会先检查是否已有指定路径的文件，如果已有，返回已有文件；
    ///     否则，创建一个新的，添加到集合中并返回。
    /// </remarks>
    public bool TryLoadSourceFile(string path,
        [MaybeNullWhen(false)] out SourceTextFile source)
    {
        return TryLoadSourceFile(
            DocumentUriCache.GetCached(Path.GetFullPath(path, Root)),
            out source);
    }


    /// <summary>
    ///     移除指定URI的源文件。
    /// </summary>
    public void RemoveFile(DocumentUri uri)
    {
        if (!_files.Remove(uri, out SourceTextFile? source))
        {
            return;
        }

        // 移除引用关系
        foreach (SourceTextFile? reference in source.GetReferencedFiles())
        {
            reference.RemoveIncludeFile(source);
        }

        foreach (SourceTextFile? include in source.GetIncludeFiles())
        {
            include.RemoveReferenceFile(source);
        }
    }


    /// <summary>
    ///     移除指定路径的源文件
    /// </summary>
    public void RemoveFile(string path)
    {
        RemoveFile(DocumentUriCache.GetCached(path));
    }

    private record ReferenceDirectiveInfo(string TargetFileName, SourceTextFile Source);
}